Задание 1

kaggle task

Есть датасет с характеристиками покупателей в магазине.
Требуется разбить покупателей на k кластеров (выбор k остается за тобой), посчитать силуэт и визуализировать полученные кластеры

Выбор k должен быть обоснован, применять можно абсолютно любые алгоритмы.

Заметка: можно генерировать новые признаки и/или не использовать все имеющиеся, да и вообще делать все, что угодно и не противоречит здравому смыслу :)

Не забывай фиксировать random_state для воспроизводимости результатов, где это требуется

In [1]:
import matplotlib.pyplot as plt

import numpy as np
import pandas as pd

from sklearn.cluster import KMeans, AgglomerativeClustering, DBSCAN

from MulticoreTSNE import MulticoreTSNE as TSNE
from sklearn.decomposition import PCA

from sklearn.metrics import silhouette_score

from pathlib import Path

##other libraries on need##
from sklearn.neighbors import NearestNeighbors
from random import sample
from numpy.random import uniform
import random
import plotly.graph_objs as go
import plotly as py
from scipy.cluster.hierarchy import dendrogram
from IPython.display import Image
import seaborn as sns
In [2]:
SEED = 42
In [3]:
df = pd.read_csv(Path('/Users/main/Мега_Задание_4/data/mall_customers.csv'))
print(df.shape)
df.head()
(200, 5)
Out[3]:
CustomerID Gender Age Annual Income (k$) Spending Score (1-100)
0 1 Male 19 15 39
1 2 Male 21 15 81
2 3 Female 20 16 6
3 4 Female 23 16 77
4 5 Female 31 17 40
In [4]:
data = df.copy()
#вначалае избавимся от CustomerID, тк он равен индексу в датафрейме + 1
data = data.drop(['CustomerID'],axis = 1)
#бинаризируем пол: male = 1, female = 0
data.Gender = (data.Gender == 'Male').astype(int)
#поменяем названия столюцов Annual Income и Spending Score, чтобы к ним было удобно обращаться
data = data.rename(columns = {'Annual Income (k$)': 'Income','Spending Score (1-100)': 'SS'}, inplace = False)
data.head()
Out[4]:
Gender Age Income SS
0 1 19 15 39
1 1 21 15 81
2 0 20 16 6
3 0 23 16 77
4 0 31 17 40
In [5]:
#проверяем, есть ли пустые ячейки - всё, обработали датафрейм
data.isnull().sum()
Out[5]:
Gender    0
Age       0
Income    0
SS        0
dtype: int64
In [6]:
#проверим, существует ли кластерная структура - статистика Хопкинса
def hop_stat(X):
    n, d = X.shape[0], X.shape[1]
    m = int(0.1 * n)
    nbrs = NearestNeighbors(n_neighbors=1).fit(X)
    random.seed(SEED)
    rand_X = sample(range(n), m)
    #print(rand_X)
    u_i = []
    w_i = []

    for j in range(m):
        random.seed(SEED)
        u_dist = nbrs.kneighbors((uniform(np.amin(X,axis=0),np.amax(X,axis=0))).reshape(1, -1), 2, return_distance=True)[0]
        u_i.append(u_dist[0][1])
        
        w_dist = nbrs.kneighbors(X[rand_X[j]:rand_X[j] + 1].to_numpy().reshape(1, -1), 2, return_distance=True)[0]
        w_i.append(w_dist[0][1])

    H = sum(w_i) / (sum(u_i) + sum(w_i))
    return H
print('Hopkins statistics =',hop_stat(data))
Hopkins statistics = 0.2927794337190973

Кластерная структура существует. Запишем также функцию поика инерции.

In [7]:
def inertia(X):
    inertia = []
    for k in range(1, 12):
        kmeans = KMeans(n_clusters=k, random_state=1).fit(X)
        inertia.append(kmeans.inertia_)
    fig = plt.figure(figsize=(14, 6))
    plt.plot(range(1, 12), inertia, marker='s');
    fig.set(facecolor = 'white')
    plt.title('Inertia', fontsize=13)
    plt.xlabel('$k$')
    plt.ylabel('$\Phi_0$');
    return

Кластерная структура существует

Уменьшение размерности

Уменьшим размерность с помошью t-SNE до 2-D и попробуем построить все 3 типа кластеризации с этими данными.

In [8]:
%%time
data_reduc = TSNE(n_components=2, random_state=SEED, metric='euclidean', n_jobs=-1).fit_transform(data)
CPU times: user 770 ms, sys: 6.29 ms, total: 776 ms
Wall time: 779 ms
In [9]:
fig = plt.figure(figsize=(8, 8))
fig.set(facecolor = 'white')
plt.scatter(data_reduc[:, 0], data_reduc[:, 1], c='g', s=20, cmap=plt.cm.get_cmap('nipy_spectral', 10))
plt.title('t-SNE projection', fontsize=13);

Получили вот такие данные. Попробуем кластеризовать с помощью K-means.

K-means

In [10]:
inertia(data_reduc)

По графику видно, что возможные точки K для деления - это 4, 5, 6. Рассмотрим их все и посчитаем коэффициент силуэта. Для достоверности посмотрим K = 7, чтобы увидеть, что силуэт для него меньше, чем для предыдущего.

In [11]:
def fit_kmeans(X,K):
    kmeans = KMeans(n_clusters = K, random_state = SEED).fit(X)
    yhat_kmeans = kmeans.predict(X)
    
    #print('Для K =',K,'Silhouette score =',silhouette_score(X, yhat_kmeans))
    return silhouette_score(X, yhat_kmeans)

def plot_kmeans(X, K):
    kmeans = KMeans(n_clusters = K, random_state = SEED).fit(X)
    yhat_kmeans = kmeans.predict(X)
    
    #print('Для K =',K,'Silhouette score =',silhouette_score(X, yhat_kmeans))
    
    colors = ['darkkhaki' if x==0 else 'indianred' if x==1 else 'seagreen' 
              if x==2 else 'darkgoldenrod' if x ==3 else 'cornflowerblue' if x == 4 else 'plum' for x in yhat_kmeans]
    
    fig = plt.figure(figsize=(8,8))
    fig.set(facecolor = 'white')
    for i in range(kmeans.cluster_centers_.shape[0]):
        plt.plot(kmeans.cluster_centers_[i, 0], kmeans.cluster_centers_[i, 1], 'rX')
    title = 'Kmeans для K = '+str(K)+' имеет Silhouette = '+str(round(silhouette_score(X, yhat_kmeans),5))
    plt.title(title, fontsize=13)
    plt.scatter(X[:,0], X[:,1], c=colors, picker=True);
    return silhouette_score(X, yhat_kmeans)

maximum, K = -1, 0
for i in range(5,8):
    print('Для K =',i,'Silhouette score =',fit_kmeans(data_reduc,i))
    if fit_kmeans(data_reduc,i) > maximum:
        maximum = fit_kmeans(data_reduc,i)
        K = i
print('Best: Для K =',K,'Silhouette score = ',plot_kmeans(data_reduc,K))
Для K = 5 Silhouette score = 0.6195643603381231
Для K = 6 Silhouette score = 0.660432119105476
Для K = 7 Silhouette score = 0.6308564550794425
Best: Для K = 6 Silhouette score =  0.660432119105476

Видим, что максимальный силуэт получаем в случае K = 6 и он равен Silhouette score = 0.660432119105476. Теперь попробуем dbscan.

DBSCAN

In [12]:
def dbs(X,e,n):
    
    dbscan = DBSCAN(eps = e, min_samples = n).fit(X)
    yhat_dbscan = dbscan.labels_
    return silhouette_score(X, yhat_dbscan)

maximum, eps, min_samples = -1, 0., 0
for i in range(2,11):
    for j in np.arange(0.9,10.1,0.1):
        #print(dbs(data_reduc,j,i))
        if dbs(data_reduc,j,i) > maximum:
            maximum = dbs(data_reduc,j,i)
            eps = j
            min_samples = i
        
print('Best SilScore for dbscan =',maximum,'when eps =',eps,', min_samples =',min_samples)
Best SilScore for dbscan = 0.6610464957895927 when eps = 1.9 , min_samples = 2

Построим график с наилучшеми параметрами.

In [13]:
dbscan = DBSCAN(eps=1.9, min_samples=2, n_jobs=-1).fit(data_reduc)
yhat_dbscan = dbscan.labels_

#plot
colors = ['darkkhaki' if x==0 else 'indianred' if x==1 else 'seagreen' 
              if x==2 else 'darkgoldenrod' if x ==3 else 'cornflowerblue' if x == 4 else 'plum' for x in yhat_dbscan]

fig = plt.figure(figsize=(8,8))
plt.title('DBSCAN', fontsize=13)
fig.set(facecolor = 'white')
plt.scatter(data_reduc[:,0], data_reduc[:,1], c=colors, picker=True);

Как мы видим, разбиение, конечно, хорошее с точки зрения коэффициента силуэта, но в большой верхней группе наблюдаются подгруппы, которые DBSCAN не выделил.

Агломеративная иерархичекая кластеризация

In [14]:
def plot_dendrogram(model, **kwargs):
    counts = np.zeros(model.children_.shape[0])
    n_samples = len(model.labels_)
    for i, merge in enumerate(model.children_):
        current_count = 0
        for child_idx in merge:
            if child_idx < n_samples:
                current_count += 1 
            else:
                current_count += counts[child_idx - n_samples]
        counts[i] = current_count

    linkage_matrix = np.column_stack([model.children_, model.distances_, counts]).astype(float)

    # Plot the corresponding dendrogram
    dendrogram(linkage_matrix, **kwargs)
    return
    
agclust = AgglomerativeClustering(n_clusters=None, distance_threshold=1).fit(data_reduc)
    
fig = plt.figure(figsize=(7, 7))
fig.set(facecolor = 'white')
plot_dendrogram(agclust)
In [15]:
def fit_agclust(data_reduc,num_clus):
    agclust = AgglomerativeClustering(n_clusters=num_clus, distance_threshold=None).fit(data_reduc)
    yhat_agclust = agclust.labels_
    
    return silhouette_score(data_reduc, yhat_agclust)

maximum, i = -1, 0
for i in range(4,8):
    if fit_agclust(data_reduc,i) > maximum:
        maximum = fit_agclust(data_reduc,i)
        cnt = i
print('Best: Для num_clus =',cnt,'Silhouette score =',maximum)
Best: Для num_clus = 6 Silhouette score = 0.6341116920506557
In [16]:
def plot_agclust(data_reduc,num_clus):
    agclust = AgglomerativeClustering(n_clusters=num_clus, distance_threshold=None).fit(data_reduc)
    yhat_agclust = agclust.labels_
    
    colors = ['darkkhaki' if x==0 else 'indianred' if x==1 else 'seagreen' 
                  if x==2 else 'darkgoldenrod' if x ==3 else 'cornflowerblue' if x == 4 else 'plum' if x ==5 
                  else 'gray' for x in yhat_agclust]

    fig = plt.figure(figsize=(8,8))
    fig.set(facecolor = 'white')
    plt.title('Агломеративная иерархическая кластеризация')
    plt.scatter(data_reduc[:,0], data_reduc[:,1], c=colors, picker=True);

    print('Для num_clus =',num_clus,'Silhouette score =',silhouette_score(data_reduc, yhat_agclust))
    return
plot_agclust(data_reduc,6)
Для num_clus = 6 Silhouette score = 0.6341116920506557

На примере аглмеративной кластеризации можно посмотреть, что за кластеры у нас получились.

In [17]:
agclust = AgglomerativeClustering(n_clusters=6, distance_threshold=None).fit(data_reduc)
yhat_agclust = agclust.labels_
X = pd.Series(data_reduc[:,0])
Y = pd.Series(data_reduc[:,1])

data['X'] = X
data['Y'] = Y
data['yhat_agclust'] = pd.Series(yhat_agclust)
X_n = []
Y_n = []
for i in range(6):
    print('')
    print('В кластере',i,':')
    X_n.append(data[data.yhat_agclust == i]['X'].mean())
    Y_n.append(data[data.yhat_agclust == i]['Y'].mean())
    for j in data.columns:
        print('Cредний',j,'равен',data[data.yhat_agclust == i][j].mean())
       
В кластере 0 :
Cредний Gender равен 0.35
Cредний Age равен 27.85
Cредний Income равен 56.75
Cредний SS равен 48.45
Cредний X равен -0.5593194041470297
Cредний Y равен 2.188300051266389
Cредний yhat_agclust равен 0.0

В кластере 1 :
Cредний Gender равен 0.44680851063829785
Cредний Age равен 55.255319148936174
Cредний Income равен 54.04255319148936
Cредний SS равен 49.659574468085104
Cредний X равен 4.016835038030037
Cредний Y равен 6.487333982738168
Cредний yhat_agclust равен 1.0

В кластере 2 :
Cредний Gender равен 0.5625
Cредний Age равен 41.0
Cредний Income равен 89.40625
Cредний SS равен 15.59375
Cредний X равен 7.740500984920129
Cредний Y равен -2.4143989108782793
Cредний yhat_agclust равен 2.0

В кластере 3 :
Cредний Gender равен 0.46153846153846156
Cредний Age равен 32.69230769230769
Cредний Income равен 86.53846153846153
Cредний SS равен 82.12820512820512
Cредний X равен -6.125650351643439
Cредний Y равен -15.021874782869158
Cредний yhat_agclust равен 3.0

В кластере 4 :
Cредний Gender равен 0.4
Cредний Age равен 24.85
Cредний Income равен 24.95
Cредний SS равен 81.0
Cредний X равен -7.40130989599749
Cредний Y равен 2.8126431924076996
Cредний yhat_agclust равен 4.0

В кластере 5 :
Cредний Gender равен 0.4090909090909091
Cредний Age равен 44.31818181818182
Cредний Income равен 25.772727272727273
Cредний SS равен 20.272727272727273
Cредний X равен -1.2358154774968624
Cредний Y равен 9.74655993602268
Cредний yhat_agclust равен 5.0

По средним координатам, можно понять, какой номер у каждого из нарисованных кластеров. Таким образом можно интерпретировать кластеры даже в случае понижения размерности.

In [18]:
agclust = AgglomerativeClustering(n_clusters=6, distance_threshold=None).fit(data_reduc)
yhat_agclust = agclust.labels_
    
colors = ['darkkhaki' if x==0 else 'indianred' if x==1 else 'seagreen' 
                if x==2 else 'darkgoldenrod' if x ==3 else 'cornflowerblue' if x == 4 else 'plum' if x ==5 
                else 'gray' for x in yhat_agclust]

fig = plt.figure(figsize=(8,8))
fig.set(facecolor = 'white')
plt.title('Агломеративная кластеризация + идентификация кластеров')
for i in range(6):
    title = 'Кластер ' + str(i)
    plt.annotate(title, xy = (X_n[i], Y_n[i]), fontsize = 14, weight = 'bold')
plt.scatter(data_reduc[:,0], data_reduc[:,1], c=colors, picker=True);

Рассмотрим, какие коэффициенты силуэта мы получили для данных при уменьшении размерности.

In [19]:
print('Best: K-means при K = 6 Silhouette score =', fit_kmeans(data_reduc,6))
print('Best: DBSCAN при eps = 1.9 и min_samples = 2 Silhouette score =', dbs(data_reduc, 1.9, 2))
print('Best: Агломеративная иерархическая кластеризация при num_clasters = 6 Silhouette score =', fit_agclust(data_reduc,6))
Best: K-means при K = 6 Silhouette score = 0.660432119105476
Best: DBSCAN при eps = 1.9 и min_samples = 2 Silhouette score = 0.6610464957895927
Best: Агломеративная иерархическая кластеризация при num_clasters = 6 Silhouette score = 0.6341116920506557

Лучший резльтат у DBSCAN, но мы помним, что разбил он всего на 2 кластера, что говорит нам, что всё-таки K-means лучше показал себя c точки зрения деления на более мелкие класетры.

Удаление не влияющих на кластеризацию столбцов

Рассмотрим, как ведут себя данные, если мы разделим их по полу.

In [20]:
data = data.drop(['X','Y','yhat_agclust'],axis = 1)
In [21]:
sns.set_style("dark")
pic = sns.pairplot(data, hue='Gender',palette = {0:'goldenrod',1:'royalblue'}, aspect=1.5)
pic.fig.suptitle('Data depending on gender',y = 1.05)
plt.show()

Из графиков выше видно, что пол не сильно влияет на данные. Поэтому этот столбец можно выкинуть и посмотреть 3-D кластеризацию.

K-means 3-D

In [22]:
data_gender = data.drop(['Gender'], axis=1)
inertia(data_gender) 
In [23]:
for K in range(5,8):
    print('Для K =',K,'Silhouette score =',fit_kmeans(data_gender, K))
Для K = 5 Silhouette score = 0.44428597560893024
Для K = 6 Silhouette score = 0.4523443947724053
Для K = 7 Silhouette score = 0.4412807576186172
In [24]:
algorithm = KMeans(n_clusters = 6 ,init='k-means++', n_init = 10 ,max_iter=300, 
                        tol=0.0001,  random_state= SEED  , algorithm='elkan') 
algorithm.fit(data_gender)
labels = algorithm.labels_

print('Silhouette score',silhouette_score(data_gender, labels))
colors = ['darkkhaki' if x==0 else 'indianred' if x==1 else 'seagreen' 
                  if x==2 else 'darkgoldenrod' if x ==3 else 'cornflowerblue' if x == 4 else 'plum' if x ==5 
                  else 'gray' for x in labels]
sex_colors = ['royalblue' if x == 1 else 'goldenrod' for x in data.Gender]
data_gender['labels'] =  labels
trace1 = go.Scatter3d(x = data_gender.Age, y = data_gender.SS, z = data_gender.Income, mode='markers',
                     marker=dict(color = sex_colors, size= 7,line=dict(color= colors,width= 13),opacity=0.9))
dat = [trace1]
layout = go.Layout(title = 'K-means 3-D',scene = dict(xaxis = dict(title  = 'Age'), yaxis = dict(title  = 'Spending Score'),
                                                  zaxis = dict(title  = 'Annual Income')))
fig = go.Figure(data=dat, layout=layout)
py.offline.iplot(fig)
Silhouette score 0.4523443947724053

Получили вот такую 3-D кластеризаицию с помощью K-means (синий центр - мужчины, желтый - женщины). Однако, самый лучший коэффициент силуэта ниже, чем был при уменьшении размерности.

Агломеративная иерархическая кластеризация 3-D

In [25]:
agclust3d = AgglomerativeClustering(n_clusters=None, distance_threshold=1).fit(data_gender)
fig = plt.figure(figsize=(7, 7))
plt.title('Дендограмма',fontsize = 12)
fig.set(facecolor = 'white')
plot_dendrogram(agclust3d)
In [26]:
maximum, i = -1, 0
for i in range(4,8):
    if fit_agclust(data_gender,i) > maximum:
        maximum = fit_agclust(data_gender,i)
        cnt = i
print('Best: Для num_clus =',cnt,'Silhouette score =',maximum)
Best: Для num_clus = 6 Silhouette score = 0.44509801181403574
In [27]:
algorithm = AgglomerativeClustering(n_clusters = 6, distance_threshold=None)
algorithm.fit(data_gender)
labels = algorithm.labels_

colors = ['darkkhaki' if x==0 else 'indianred' if x==1 else 'seagreen' 
                  if x==2 else 'darkgoldenrod' if x ==3 else 'cornflowerblue' if x == 4 else 'plum' if x ==5 
                  else 'gray' for x in labels]
sex_colors = ['royalblue' if x == 1 else 'goldenrod' for x in data.Gender]
print('Silhouette score',silhouette_score(data_gender, labels))
data_gender['labels'] =  labels
trace1 = go.Scatter3d(x = data_gender.Age, y = data_gender.SS, z = data_gender.Income, mode='markers',
                     marker=dict(color = sex_colors, size= 7,line=dict(color = colors,width= 13),opacity=0.9))
dat = [trace1]
layout = go.Layout(title = 'Агломеративная кластеризация 3-D',scene = dict(xaxis = dict(title  = 'Age'), yaxis = dict(title  = 'Spending Score'),
                                                  zaxis = dict(title  = 'Annual Income')))
fig = go.Figure(data=dat, layout=layout)
py.offline.iplot(fig)
Silhouette score 0.44509801181403574

На 3-D графиках точки с синим центром - мужчины, а точки с желтым - женщины. Несмотря на низкий коэффициент силуэта, при 3-D кластеризации намного проще идентифицировать кластеры.

In [28]:
for i in range(6):
    print('В группе',i,':')
    for j in data_gender.columns:
        print('Cредний',j,'равен',data_gender[data_gender['labels'] == i][j].mean())
В группе 0 :
Cредний Age равен 41.68571428571428
Cредний Income равен 88.22857142857143
Cредний SS равен 17.285714285714285
Cредний labels равен 0.0
В группе 1 :
Cредний Age равен 43.9
Cредний Income равен 24.45
Cредний SS равен 19.1
Cредний labels равен 1.0
В группе 2 :
Cредний Age равен 32.69230769230769
Cредний Income равен 86.53846153846153
Cредний SS равен 82.12820512820512
Cредний labels равен 2.0
В группе 3 :
Cредний Age равен 24.80952380952381
Cредний Income равен 25.61904761904762
Cредний SS равен 80.23809523809524
Cредний labels равен 3.0
В группе 4 :
Cредний Age равен 54.56
Cредний Income равен 53.52
Cредний SS равен 49.16
Cредний labels равен 4.0
В группе 5 :
Cредний Age равен 25.97142857142857
Cредний Income равен 55.6
Cредний SS равен 48.77142857142857
Cредний labels равен 5.0

Выведем лучшие коэффициенты силуэта для 3-D кластеризации.

In [29]:
data_gender = data_gender.drop(['labels'],axis = 1)
In [30]:
print('Best: K-means 3-D при K = 6 Silhouette score =', fit_kmeans(data_gender,6))
print('Best: Агломеративная иерархическая кластеризация 3-D при num_clasters = 6 Silhouette score =', fit_agclust(data_gender,6))
Best: K-means 3-D при K = 6 Silhouette score = 0.4523443947724053
Best: Агломеративная иерархическая кластеризация 3-D при num_clasters = 6 Silhouette score = 0.44308912983504156

Итог: разбивать данные нужно на 6 кластеров (это подтвердила как кластеризация с уменьшением размености, так и кластеризации без учета пола). Лучший коэффициент ситлуэта получили при уменьшении размерности и использовании K-means

Задание 2 (по желанию)

Возьмем стандартный датасет ирисов, состоящий из 4-х признаков длины/ширины внутренней и наружной долей околоцветника и таргета - вид цветка (kind).

1.Требуется кластеризовать цветки (да-да, количество кластеров уже известно), посчитать accuracy. Выбор алгоритма опять же на усмотрение (может быть несколько)
Важно: метки могут расставиться в другом порядке относительно истинного таргета, то есть после кластеризации будут получены 0, а это на самом деле 1, то есть надо сделать отображение $0 \rightarrow 1$ , то же самое касается и других меток.
Поэтому, получив метки кластеров, стоит перебрать все возможные их перестановки. В этом поможет itertools.permutations

2.Воспользоваться любым/любыми пройденными/известными алгоритмами классификации и посчитать accuracy
(Например, логистической регрессией)

In [31]:
from sklearn import datasets
import itertools

##other libraries on need##
from sklearn.metrics import accuracy_score
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
In [32]:
df = datasets.load_iris()
df = pd.DataFrame(np.hstack([df['data'], df['target'].reshape(-1,1)])
             , columns=['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'kind'])

print('Count of unique flowers', df['kind'].unique().shape[0])
df.head()
Count of unique flowers 3
Out[32]:
sepal_length sepal_width petal_length petal_width kind
0 5.1 3.5 1.4 0.2 0.0
1 4.9 3.0 1.4 0.2 0.0
2 4.7 3.2 1.3 0.2 0.0
3 4.6 3.1 1.5 0.2 0.0
4 5.0 3.6 1.4 0.2 0.0
In [33]:
df.tail()
Out[33]:
sepal_length sepal_width petal_length petal_width kind
145 6.7 3.0 5.2 2.3 2.0
146 6.3 2.5 5.0 1.9 2.0
147 6.5 3.0 5.2 2.0 2.0
148 6.2 3.4 5.4 2.3 2.0
149 5.9 3.0 5.1 1.8 2.0

Кластеризация

In [34]:
def validate_with_mappings(y_true, y_pred):
    '''
    Check all possible permutations to maximize accuracy
    '''
    
    l = []
    permutations = itertools.permutations([0, 1, 2])
    for a, b, c in permutations:
        mapping = {0 : a, 1: b, 2: c}
        mapped_preds = [mapping[pred] for pred in y_pred]
        l.append((mapping, sum(mapped_preds == y_true) / len(y_true)))
                 
    return l
In [35]:
X = df.iloc[:, :-1]
y = df['kind']

##твой код с выбором алгоритма ##
kmeans = KMeans(n_clusters = 3, random_state = SEED).fit(X)
y_pred = kmeans.predict(X)
#print('y_pred = ',y_pred)
#print('y_true = ',y.to_numpy())
maximum, ind = 0, 0
for i in range(len(validate_with_mappings(y, y_pred))):
    if validate_with_mappings(y, y_pred)[i][1] > maximum:
        maximum = validate_with_mappings(y, y_pred)[i][1]
        ind = i
print(validate_with_mappings(y, y_pred)[ind])
({0: 1, 1: 0, 2: 2}, 0.8933333333333333)
In [36]:
dic = validate_with_mappings(y, y_pred)[ind][0]
y_pred_pd = pd.Series(y_pred)
y_pred_pd = y_pred_pd.apply(lambda x: dic[0] if x ==0 else dic[1] if x == 1 else dic[2])
#print(y_pred_pd)
print('K-means Accuracy score =',accuracy_score(y,y_pred_pd))
K-means Accuracy score = 0.8933333333333333

Классификация

In [37]:
##твой код##
#сплитуем на test и train, потому что применять предсказания к выборке, на которой обучаслись = переобученная модель
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = SEED)

LR = LogisticRegression(penalty='l2',random_state = SEED,max_iter=10000)
LR.fit(X_train,y_train)
y_pred = LR.predict(X_test)
#print('y_true =',y_test.to_numpy())
#print('y_pred =',y_pred)
print('LogReg Accuracy score = ',accuracy_score(y_test,y_pred)) #такой хороший accuracy только из-за маленького датафрейма (на мой взгляд)
LogReg Accuracy score =  1.0
In [38]:
print('Классификация лучше кластеризации на',round((accuracy_score(y_test,y_pred) - accuracy_score(y,y_pred_pd))*100,2),'%')
Классификация лучше кластеризации на 10.67 %

Вопрос: что оказалось лучше: алгоритм классификации или кластеризации и на сколько?
Ответ: **

Классификация лучше и это логично, потому что кластеризация нужна для разделения данных на кластеры, когда этих кластеров заранее нет. А вот классификация как раз используется для разделения данных на уже существующие классы. В данном случае имеем как раз вторую ситуацию, однако при большем размере датафрейма классификация могла сработать не так хорошо.